﻿using System;
using System.Diagnostics;
using EnvDTE;

namespace CodeJumper
{
    public delegate void ParserCompleteDelegate(DocumentParser sender);
    public delegate void ParserProgressUpdate(DocumentParser sender);
    public class DocumentParser
    {
        private readonly ApplicationConfiguration _appConfig;
        public bool HasAllInformation = false;
        public CodeElementAggregate RootElement;
        public long _recurseCount = 0;

        public event ParserCompleteDelegate ParserComplete;
        public event ParserProgressUpdate ParserProgress;

        public DocumentParser(ApplicationConfiguration appConfig)
        {
            this._appConfig = appConfig;
            if (appConfig.DocumentProjectItem != null && appConfig.DocumentFilename != null)
                HasAllInformation = true;
        }


        public bool ParseDocument()
        {
            _recurseCount = 0;

            try
            {
                Trace.WriteLine(string.Format("Parsing {0}", _appConfig.DocumentFilename));
                RootElement = new CodeElementAggregate(_appConfig);

                if (_appConfig.DocumentProjectItem.FileCodeModel == null)
                {
                    Trace.WriteLine("Unable to parse document.");
                    return false;
                }

                CodeElements elements = _appConfig.DocumentProjectItem.FileCodeModel.CodeElements;

                Stopwatch sw = new Stopwatch();
                sw.Start();
                RecurseCodeElements(elements, RootElement);
                sw.Stop();
                Trace.WriteLine(string.Format("Full reparse took {0} seconds.", sw.Elapsed.TotalSeconds));
                if (PluginConfiguration.Instance.SortType == SortType.Alphabetical)
                {
                    RootElement.SortChildrenAlphabetically();
                }

                Trace.WriteLine("Parsing complete.");
                if (ParserComplete != null)
                {
                    _appConfig.elementView.Dispatcher.BeginInvoke((Action) (() => ParserComplete(this)));
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(string.Format("Exception in ParseDocument. {0}", e.Message));
                return false;
            }

            return true;
        }


        public bool DeleteElement(CodeElement element)
        {
            string uniqueId = CodeElementHelper.GetUniqueElementId(element);

            CodeElementAggregate aggregate = RootElement.Find(element);

            if (aggregate == null)
                return false;

            if (aggregate.Parent == null)
                return false;

            aggregate.Parent.Children.Remove(aggregate);

            return true;
        }

        public CodeElementAggregate ChangeElement(CodeElement element)
        {
            string uniqueId = CodeElementHelper.GetUniqueElementId(element);

            CodeElementAggregate aggregate = RootElement.Find(element);

            if (aggregate == null)
                return null;

            aggregate.Clear();

            RecurseCodeElements(GetMembers(element), aggregate);

            return aggregate;

        }

        public void RecurseCodeElements(CodeElements elements, CodeElementAggregate aggregate)
        {
            if (elements == null)
                return;
            _recurseCount++;

            if (_recurseCount % 2 == 0)
            {
                if (ParserProgress != null)
                    _appConfig.elementView.Dispatcher.BeginInvoke((Action)(() => ParserProgress(this)));
            }

            foreach (CodeElement element in elements)
            {
                System.Threading.Thread.Sleep(1);

                if (element.Kind == vsCMElement.vsCMElementClass)
                {
                    Trace.WriteLine(string.Format("Name: {0}", element.Name));
                    RecurseCodeElements(GetMembers(element), aggregate.Add(element));
                }

                if (element.Kind == vsCMElement.vsCMElementInterface)
                {
                    RecurseCodeElements(GetMembers(element), aggregate.Add(element));
                }

                if (element.Kind == vsCMElement.vsCMElementStruct)
                {
                    RecurseCodeElements(GetMembers(element), aggregate.Add(element));
                }

                if (element.Kind == vsCMElement.vsCMElementNamespace)
                {
                    Trace.WriteLine(string.Format("Name: {0}", element.Name));
                    RecurseCodeElements(GetMembers(element), aggregate);
                }

                if (element.Kind == vsCMElement.vsCMElementEnum)
                {
                    RecurseCodeElements(GetMembers(element), aggregate.Add(element));
                }

                if (element.Kind == vsCMElement.vsCMElementFunction ||
                    element.Kind == vsCMElement.vsCMElementProperty ||
                    element.Kind == vsCMElement.vsCMElementVariable ||
                    element.Kind == vsCMElement.vsCMElementDelegate ||
                    element.Kind == vsCMElement.vsCMElementEvent)
                {
                    var func = element as CodeFunction;
                    aggregate.Add(element);
                }
            }
        }

        private CodeElements GetMembers(CodeElement element)
        {
            if (element == null)
                return null;

            if (element.Kind == vsCMElement.vsCMElementNamespace)
            {
                return (element as CodeNamespace).Members;
            }

            if (element.Kind == vsCMElement.vsCMElementClass)
            {
                return (element as CodeClass).Members;
            }

            if (element.Kind == vsCMElement.vsCMElementInterface)
            {
                return (element as CodeInterface).Members;
            }

            if (element.Kind == vsCMElement.vsCMElementEnum)
            {
                return (element as CodeEnum).Members;
            }

            if (element.Kind == vsCMElement.vsCMElementStruct)
            {
                return (element as CodeStruct).Members;
            }
            return null;
        }
    }
}